home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / lib / rhythmbox / plugins / jamendo / JamendoSource.py < prev    next >
Encoding:
Python Source  |  2009-04-07  |  14.0 KB  |  409 lines

  1. # -*- coding: utf-8 -*-
  2.  
  3. # JamendoSource.py
  4. #
  5. # Copyright (C) 2007 - Guillaume Desmottes
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2, or (at your option)
  10. # any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program; if not, write to the Free Software
  19. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  20.  
  21. # Parts from "Magnatune Rhythmbox plugin" (stolen from rhythmbox's MagnatuneSource.py)
  22. #     Copyright (C), 2006 Adam Zimmerman <adam_zimmerman@sfu.ca>
  23.  
  24. import rb, rhythmdb
  25. from JamendoSaxHandler import JamendoSaxHandler
  26. import JamendoConfigureDialog
  27.  
  28. import os
  29. import gobject
  30. import gtk.glade
  31. import gnome, gconf
  32. import xml
  33. import gzip
  34. import datetime
  35.  
  36. # URIs
  37.  
  38. jamendo_song_info_uri = "http://img.jamendo.com/data/dbdump_artistalbumtrack.xml.gz"
  39.  
  40. mp32_uri = "http://api.jamendo.com/get2/bittorrent/file/plain/?type=archive&class=mp32&album_id="
  41. ogg3_uri = "http://api.jamendo.com/get2/bittorrent/file/plain/?type=archive&class=ogg3&album_id="
  42.  
  43.  
  44. #  MP3s for streaming : http://api.jamendo.com/get2/stream/track/redirect/?id={TRACKID}&streamencoding=mp31
  45. # OGGs for streaming : http://api.jamendo.com/get2/stream/track/redirect/?id={TRACKID}&streamencoding=ogg2
  46.  
  47. # .torrent file for download (MP3 archive) : http://api.jamendo.com/get2/bittorrent/file/plain/?album_id={ALBUMID}&type=archive&class=mp32
  48. # .torrent file for download (OGG archive) : http://api.jamendo.com/get2/bittorrent/file/plain/?album_id={ALBUMID}&type=archive&class=ogg3
  49.  
  50. # Album Covers are available here: http://api.jamendo.com/get2/image/album/redirect/?id={ALBUMID}&imagesize={100-600}
  51.  
  52. stream_url = "http://api.jamendo.com/get2/stream/track/redirect/?id=%s&streamencoding=ogg2"
  53. artwork_url = "http://api.jamendo.com/get2/image/album/redirect/?id=%s&imagesize=200"
  54. artist_url = "http://www.jamendo.com/get/artist/id/album/page/plain/"
  55.  
  56. genre_id3 = ["Blues","Classic Rock","Country","Dance","Disco","Funk","Grunge","Hip-Hop","Jazz","Metal","New Age","Oldies","Other","Pop","R&B","Rap","Reggae","Rock","Techno","Industrial","Alternative","Ska","Death Metal","Pranks","Soundtrack","Euro-Techno","Ambient","Trip-Hop","Vocal","Jazz+Funk","Fusion","Trance","Classical","Instrumental","Acid","House","Game","Sound Clip","Gospel","Noise","AlternRock","Bass","Soul","Punk","Space","Meditative","Instrumental Pop","Instrumental Rock","Ethnic","Gothic","Darkwave","Techno-Industrial","Electronic","Pop-Folk","Eurodance","Dream","Southern Rock","Comedy","Cult","Gangsta","Top 40","Christian Rap","Pop/Funk","Jungle","Native American","Cabaret","New Wave","Psychadelic","Rave","Showtunes","Trailer","Lo-Fi","Tribal","Acid Punk","Acid Jazz","Polka","Retro","Musical","Rock & Roll","Hard Rock","Folk","Folk-Rock","National Folk","Swing","Fast Fusion","Bebob","Latin","Revival","Celtic","Bluegrass","Avantgarde","Gothic Rock","Progressive Rock","Psychedelic Rock","Symphonic Rock","Slow Rock","Big Band","Chorus","Easy Listening","Acoustic","Humour","Speech","Chanson","Opera","Chamber Music","Sonata","Symphony","Booty Bass","Primus","Porn Groove","Satire","Slow Jam","Club","Tango","Samba","Folklore","Ballad","Power Ballad","Rhythmic Soul","Freestyle","Duet","Punk Rock","Drum Solo","Acapella","Euro-House","Dance Hall"]
  57.  
  58. class JamendoSource(rb.BrowserSource):
  59.     __gproperties__ = {
  60.         'plugin': (rb.Plugin, 'plugin', 'plugin', gobject.PARAM_WRITABLE|gobject.PARAM_CONSTRUCT_ONLY),
  61.     }
  62.  
  63.     def __init__(self):
  64.  
  65.         rb.BrowserSource.__init__(self, name=_("Jamendo"))
  66.  
  67.         # catalogue stuff
  68.         self.__db = None
  69.         self.__saxHandler = None
  70.         self.__activated = False
  71.         self.__notify_id = 0
  72.         self.__update_id = 0
  73.         self.__info_screen = None
  74.         self.__updating = True
  75.         self.__load_current_size = 0
  76.         self.__load_total_size = 0
  77.         self.__db_load_finished = False
  78.  
  79.         self.__catalogue_loader = None
  80.         self.__catalogue_check = None
  81.  
  82.         self.__jamendo_dir = rb.find_user_cache_file("jamendo")
  83.         if os.path.exists(self.__jamendo_dir) is False:
  84.             os.makedirs(self.__jamendo_dir, 0700)
  85.  
  86.         self.__local_catalogue_path = os.path.join(self.__jamendo_dir, "dbdump.xml")
  87.         self.__local_catalogue_temp = os.path.join(self.__jamendo_dir, "dbdump.xml.tmp")
  88.  
  89.     def do_set_property(self, property, value):
  90.         if property.name == 'plugin':
  91.             self.__plugin = value
  92.         else:
  93.             raise AttributeError, 'unknown property %s' % property.name
  94.  
  95.     def do_impl_get_browser_key (self):
  96.         return "/apps/rhythmbox/plugins/jamendo/show_browser"
  97.  
  98.     def do_impl_get_paned_key (self):
  99.         return "/apps/rhythmbox/plugins/jamendo/paned_position"
  100.  
  101.     def do_impl_pack_paned (self, paned):
  102.         self.__paned_box = gtk.VBox(False, 5)
  103.         self.pack_start(self.__paned_box)
  104.         self.__paned_box.pack_start(paned)
  105.  
  106.     #
  107.     # RBSource methods
  108.     #
  109.  
  110.     def do_impl_show_entry_popup(self):
  111.         self.show_source_popup ("/JamendoSourceViewPopup")
  112.  
  113.     def do_impl_get_ui_actions(self):
  114.         return ["JamendoDownloadAlbum","JamendoDonateArtist"]
  115.  
  116.  
  117.     def do_impl_get_status(self):
  118.         if self.__updating:
  119.             if self.__load_total_size > 0:
  120.                 progress = min (float(self.__load_current_size) / self.__load_total_size, 1.0)
  121.             else:
  122.                 progress = -1.0
  123.             return (_("Loading Jamendo catalogue"), None, progress)
  124.         else:
  125.             qm = self.get_property("query-model")
  126.             return (qm.compute_status_normal("%d song", "%d songs"), None, 0.0)
  127.  
  128.     def do_impl_activate(self):
  129.         if not self.__activated:
  130.             shell = self.get_property('shell')
  131.             self.__db = shell.get_property('db')
  132.             self.__entry_type = self.get_property('entry-type')
  133.  
  134.             self.__activated = True
  135.             self.__show_loading_screen (True)
  136.  
  137.             # start our catalogue updates
  138.             self.__update_id = gobject.timeout_add(6 * 60 * 60 * 1000, self.__update_catalogue)
  139.             self.__update_catalogue()
  140.  
  141.             sort_key = gconf.client_get_default().get_string(JamendoConfigureDialog.gconf_keys['sorting'])
  142.             if not sort_key:
  143.                 sort_key = "Artist,ascending"
  144.             self.get_entry_view().set_sorting_type(sort_key)
  145.  
  146.         rb.BrowserSource.do_impl_activate (self)
  147.  
  148.     def do_impl_delete_thyself(self):
  149.         if self.__update_id != 0:
  150.             gobject.source_remove (self.__update_id)
  151.             self.__update_id = 0
  152.  
  153.         if self.__notify_id != 0:
  154.             gobject.source_remove (self.__notify_id)
  155.             self.__notify_id = 0
  156.  
  157.         if self.__catalogue_loader:
  158.             self.__catalogue_loader.cancel()
  159.             self.__catalogue_loader = None
  160.  
  161.         if self.__catalogue_check:
  162.             self.__catalogue_check.cancel()
  163.             self.__catalogue_check = None
  164.  
  165.         gconf.client_get_default().set_string(JamendoConfigureDialog.gconf_keys['sorting'], self.get_entry_view().get_sorting_type())
  166.         rb.BrowserSource.do_impl_delete_thyself (self)
  167.  
  168.  
  169.     #
  170.     # internal catalogue downloading and loading
  171.     #
  172.  
  173.     def __catalogue_chunk_cb(self, result, total):
  174.         if not result or isinstance (result, Exception):
  175.             if result:
  176.                 # report error somehow?
  177.                 print "error loading catalogue: %s" % result
  178.  
  179.             self.__parser.close()
  180.             self.__db_load_finished = True
  181.             self.__updating = False
  182.             self.__load_db ()
  183.             self.__show_loading_screen (False)
  184.             self.__catalogue_loader = None
  185.             return
  186.  
  187.         self.__parser.feed(result)
  188.         self.__load_current_size += len(result)
  189.         self.__load_total_size = total
  190.         self.__notify_status_changed()
  191.  
  192.     def __load_catalogue(self):
  193.         print "loading catalogue %s" % self.__local_catalogue_path
  194.         self.__notify_status_changed()
  195.         self.__db_load_finished = False
  196.  
  197.         self.__saxHandler = JamendoSaxHandler()
  198.         self.__parser = xml.sax.make_parser()
  199.         self.__parser.setContentHandler(self.__saxHandler)
  200.  
  201.         self.__catalogue_loader = rb.ChunkLoader()
  202.         self.__catalogue_loader.get_url_chunks(self.__local_catalogue_path, 64*1024, True, self.__catalogue_chunk_cb)
  203.  
  204.  
  205.     def __download_catalogue_chunk_cb (self, result, total, out):
  206.         if not result:
  207.             # done downloading, unzip to real location
  208.             out.close()
  209.             catalog = gzip.open(self.__local_catalogue_temp)
  210.             out = open(self.__local_catalogue_path, 'w')
  211.  
  212.             while True:
  213.                 s = catalog.read(4096)
  214.                 if s == "":
  215.                     break
  216.                 out.write(s)
  217.  
  218.             out.close()
  219.             catalog.close()
  220.             os.unlink(self.__local_catalogue_temp)
  221.  
  222.             self.__db_load_finished = True
  223.             self.__show_loading_screen (False)
  224.             self.__catalogue_loader = None
  225.  
  226.             self.__load_catalogue ()
  227.  
  228.         elif isinstance(result, Exception):
  229.             # complain
  230.             pass
  231.         else:
  232.             out.write(result)
  233.             self.__load_current_size += len(result)
  234.             self.__load_total_size = total
  235.  
  236.         self.__notify_status_changed()
  237.  
  238.     def __download_catalogue(self):
  239.         print "downloading catalogue"
  240.         self.__updating = True
  241.         out = open(self.__local_catalogue_temp, 'w')
  242.  
  243.         self.__catalogue_loader = rb.ChunkLoader()
  244.         self.__catalogue_loader.get_url_chunks(jamendo_song_info_uri, 4*1024, True, self.__download_catalogue_chunk_cb, out)
  245.  
  246.     def __update_catalogue(self):
  247.         def update_cb (result):
  248.             self.__catalogue_check = None
  249.             if result is True:
  250.                 self.__download_catalogue()
  251.             elif self.__db_load_finished is False:
  252.                 self.__load_catalogue()
  253.  
  254.         self.__catalogue_check = rb.UpdateCheck()
  255.         self.__catalogue_check.check_for_update(self.__local_catalogue_path, jamendo_song_info_uri, update_cb)
  256.  
  257.  
  258.     def __show_loading_screen(self, show):
  259.         if self.__info_screen is None:
  260.             # load the glade stuff
  261.             gladexml = gtk.glade.XML(self.__plugin.find_file("jamendo-loading.glade"), root="jamendo_loading_scrolledwindow")
  262.             self.__info_screen = gladexml.get_widget("jamendo_loading_scrolledwindow")
  263.             self.pack_start(self.__info_screen)
  264.             self.get_entry_view().set_no_show_all (True)
  265.             self.__info_screen.set_no_show_all (True)
  266.  
  267.         self.__info_screen.set_property("visible", show)
  268.         self.__paned_box.set_property("visible", not show)
  269.  
  270.     def __load_db(self):
  271.         artists = self.__saxHandler.artists
  272.  
  273.         nbAlbums = 0
  274.         nbTracks = 0
  275.         for artist_key in artists.keys():
  276.             artist = artists[artist_key]
  277.             for album_key in artist['ALBUMS'].keys():
  278.                 nbAlbums = nbAlbums + 1
  279.                 album = artist['ALBUMS'][album_key]
  280.                 for track_key in album['TRACKS'].keys():
  281.                     nbTracks = nbTracks + 1
  282.                     track = album['TRACKS'][track_key]
  283.                     track_id = track['id']
  284.                     stream = stream_url % (track_id)
  285.                     entry = self.__db.entry_lookup_by_location (stream)
  286.                     if entry == None:
  287.                         entry = self.__db.entry_new(self.__entry_type, stream)
  288.  
  289.                     release_date = album['releasedate']
  290.                     if release_date:
  291.                         year = int(release_date[0:4])
  292.                         date = datetime.date(year, 1, 1).toordinal()
  293.                         self.__db.set(entry, rhythmdb.PROP_DATE, date)
  294.  
  295.                     self.__db.set(entry, rhythmdb.PROP_TITLE, track['name'])
  296.                     self.__db.set(entry, rhythmdb.PROP_ARTIST, artist['name'])
  297.                     try:
  298.                         genre = genre_id3[int(album['id3genre'])]
  299.                     except Exception:
  300.                         genre = _('Unknown')
  301.                         
  302.                     self.__db.set(entry, rhythmdb.PROP_GENRE, genre)
  303.                     self.__db.set(entry, rhythmdb.PROP_ALBUM, album['name'])
  304.  
  305.                     trackno = int(track['numalbum'])
  306.                     if trackno >= 0:
  307.                         self.__db.set(entry, rhythmdb.PROP_TRACK_NUMBER, trackno)
  308.  
  309.                     try:
  310.                         duration = float(track['duration'])
  311.                         self.__db.set(entry, rhythmdb.PROP_DURATION, int(duration))
  312.                     except Exception:
  313.                         # No length, nevermind
  314.                         pass
  315.                     
  316.                     # slight misuse, but this is far more efficient than having a python dict
  317.                     # containing this data.
  318.                     self.__db.set(entry, rhythmdb.PROP_MUSICBRAINZ_ALBUMID, album['id'])
  319.  
  320.         print "Nb artistes : " + str(len(artists))
  321.         print "Nb albums : " + str(nbAlbums)
  322.         print "Nb tracks : " + str(nbTracks)
  323.  
  324.         self.__db.commit()
  325.         self.__saxHandler = None
  326.  
  327.  
  328.     def __notify_status_changed(self):
  329.         def change_idle_cb():
  330.             self.notify_status_changed()
  331.             self.__notify_id = 0
  332.             return False
  333.  
  334.         if self.__notify_id == 0:
  335.             self.__notify_id = gobject.idle_add(change_idle_cb)
  336.  
  337.  
  338.     # Download album
  339.     def download_album (self):
  340.         tracks = self.get_entry_view().get_selected_entries()
  341.         format = gconf.client_get_default().get_string(JamendoConfigureDialog.gconf_keys['format'])
  342.         if not format or format not in JamendoConfigureDialog.format_list:
  343.             format = 'ogg3'
  344.  
  345.         #TODO: this should work if the album was selected in the browser
  346.         #without any track selected
  347.         if len(tracks) == 1:
  348.             track = tracks[0]
  349.             albumid = self.__db.entry_get(track, rhythmdb.PROP_MUSICBRAINZ_ALBUMID)
  350.  
  351.             formats = {}
  352.             formats["mp32"] = mp32_uri + albumid
  353.             formats["ogg3"] = ogg3_uri + albumid
  354.  
  355.             p2plink = formats[format]
  356.             l = rb.Loader()
  357.             l.get_url(p2plink, self.__download_p2plink, albumid)
  358.  
  359.     def __download_p2plink (self, result, albumid):
  360.         if result is None:
  361.             emsg = _("Error looking up p2plink for album %s on jamendo.com") % (albumid)
  362.             gtk.MessageDialog(None, 0, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, emsg).run()
  363.             return
  364.  
  365.         rb.show_uri(result)
  366.     
  367.     # Donate to Artist
  368.     def launch_donate (self):
  369.         tracks = self.get_entry_view().get_selected_entries()
  370.  
  371.         #TODO: this should work if the artist was selected in the browser
  372.         #without any track selected
  373.         if len(tracks) == 1:
  374.             track = tracks[0]
  375.             # The Album ID can be used to lookup the artist, and issue a clean redirect.
  376.             albumid = self.__db.entry_get(track, rhythmdb.PROP_MUSICBRAINZ_ALBUMID)
  377.             artist = self.__db.entry_get(track, rhythmdb.PROP_ARTIST)
  378.             url = artist_url + albumid.__str__() + "/"
  379.  
  380.             l = rb.Loader()
  381.             l.get_url(url, self.__open_donate, artist)
  382.  
  383.     def __open_donate (self, result, artist):
  384.         if result is None:
  385.             emsg = _("Error looking up artist %s on jamendo.com") % (artist)
  386.             gtk.MessageDialog(None, 0, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, emsg).run()
  387.             return
  388.         rb.show_uri(result + "donate/")
  389.  
  390.     def playing_entry_changed (self, entry):
  391.         if not self.__db or not entry:
  392.             return
  393.  
  394.         if entry.get_entry_type() != self.__db.entry_type_get_by_name("JamendoEntryType"):
  395.             return
  396.  
  397.         gobject.idle_add(self.emit_cover_art_uri, entry)
  398.  
  399.     def emit_cover_art_uri (self, entry):
  400.         stream = self.__db.entry_get (entry, rhythmdb.PROP_LOCATION)
  401.         albumid = self.__db.entry_get (entry, rhythmdb.PROP_MUSICBRAINZ_ALBUMID)
  402.         url = artwork_url % albumid
  403.  
  404.         self.__db.emit_entry_extra_metadata_notify (entry, "rb:coverArt-uri", str(url))
  405.         return False
  406.  
  407. gobject.type_register(JamendoSource)
  408.  
  409.